package Dungeoneering.Entities;

import java.awt.Point;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;

import org.powerbot.game.api.methods.Game;
import org.powerbot.game.api.util.Filter;
import org.powerbot.game.api.wrappers.Tile;
import org.powerbot.game.api.wrappers.interactive.NPC;
import org.powerbot.game.api.wrappers.node.SceneObject;
import org.powerbot.game.api.wrappers.node.SceneObject$Type;

import Dungeoneering.Utils.Utilities;
import Dungeoneering.Utils.Validator;
import Dungeoneering.api.methods.Objects;
import Dungeoneering.api.wrappers.TileArea;


/**
 * This resembles a room in the dungeon. Each room can have a max of 4 doors
 * @author Nicolaas
 *
 */
public class Room {
	
	public enum Direction { NORTH, EAST, SOUTH, WEST };
	public enum RoomType { GUARDIAN, BOSS, DEFAULT, BASE, PUZZLE, UNKNOWN};
	
	protected ArrayList<NPC> monsters;
	private boolean monstersInitialised;
	
	private LinkedHashMap<Point, SceneObject[]> locations;
	
	protected Map<Direction, Door> doors;
	private boolean doorsInitialised;
	
	private TileArea area;
	
	private RoomType type;
	
	// center tile.getPosition()
	private int centerX;
	private int centerY;
	
	// coordinates on the 8x8 grid
	private int mapX;
	private int mapY;
	
	/**
	 * Constructs a room explicitely opened
	 * @param mapX the x coordinate on our 8x8 grid
	 * @param mapY the y coordinate on our 8x8 grid
	 * @param tileX the x coordinate of the room that was used to open the room with
	 * @param tileY the y coordinate of the room that was used to open the room with
	 * @param dir the direction from the centertile of our new room of which the room was opened
	 */
	public Room(int mapX, int mapY, int tileX, int tileY, Direction dir) {
		setTileCoordinates(tileX, tileY, dir);
		setMapCoordinates(mapX, mapY);
		init();
	}
	
	/**
	 * constructs a BASE room.
	 * @param mapX the x coordinate on our 8x8 grid
	 * @param mapY the y coordinate on our 8x8 grid
	 * @param centerX the x coordinate of our center tile
	 * @param centerY the y coordinate of our center tile
	 */
	public Room(int mapX, int mapY, int centerX, int centerY) {
		this.centerX = centerX;
		this.centerY = centerY;
		setMapCoordinates(mapX, mapY);
		init();
	}
	
	private void init() {
		monstersInitialised = false;
		doorsInitialised = false;
		type = RoomType.UNKNOWN;
		monsters = new ArrayList<NPC>();
		doors = new HashMap<Direction, Door>();
		locations = new LinkedHashMap<Point, SceneObject[]>();
		defineArea();
		initializeDoors();
	}
	
	/**
	 * initializes the doors, checks the to check doorTiles to see which door is there
	 */
	private void initializeDoors() {
		while (Game.getClientState() == 12)
			Utilities.sleep(1);
		if (area.isCompletelyLoaded()) {
			SceneObject door = Objects.getTopAt(getDoorTile(Direction.NORTH), SceneObject$Type.INTERACTIVE);
			if (door != null && door.getArea().npoints == 2 && Validator.isValidDoor(door)) {
				// TODO : add to singleton mapListener
				// TODO : add roomtype
				if (Validator.isGuardianDoor(door)) {
					type = RoomType.GUARDIAN;
				}
				
			}
			doorsInitialised = true;
		} else {
			doorsInitialised = false;
		}
	}
	
	public Tile getDoorTile(Direction dir) {
		switch (dir) {
			case NORTH:
				return new Tile(centerX, centerY + 7, Game.getPlane());
			case EAST:
				return new Tile(centerX + 7, centerY, Game.getPlane());
			case SOUTH:
				return new Tile(centerX, centerY - 8, Game.getPlane());
			case WEST:
				return new Tile(centerX - 8, centerY, Game.getPlane());
			default:
				return null;
		}
	}
	
	/**
	 * gets The type of room we're dealing with
	 * @return the RoomType
	 */
	public RoomType getRoomType() {
		return type;
	}
	
	/**
	 * gets the map coordinates
	 * @return the coordinates on the map
	 */
	public Point getMapCoords() {
		return new Point(mapX, mapY);
	}
	
	/**
	 * gets the locations in this room as a list
	 * @return a list of locations in this room
	 */
	public LinkedHashMap<Point, SceneObject[]> getLocations() {
		if (locations.size() == 0)
			locations = getArea().getLocations();
		return locations;
	}
	
	public SceneObject getNearestLocation(final int id) {
		return Objects.getNearest(new Filter<SceneObject>() {

			@Override
			public boolean accept(SceneObject l) {
				return l.getId() == id && area.contains(l.getLocation());
			}
		});
	}
	
	/**
	 * checks if the room contains ALL of these locations
	 * @param ids the ids that you want to find
	 * @return true if all exist inside the room
	 */
	public boolean containsLocations(int ... ids) {
		if (ids.length == 1)
			return containsOneOfLocations(ids);
		boolean[] boolArray = new boolean[ids.length];
		Iterator<SceneObject[]> it = getLocations().values().iterator();
		while (it.hasNext()) {
			for (SceneObject l : it.next()) {
				for (int i = 0; i < ids.length; i++) {
					if (l.getId() == ids[i])
						boolArray[i] = true;
				}
			}
		}
		for (int i = 0; i < boolArray.length; i++) {
			if (!boolArray[i])
				return false;
		}
		return true;
	}
	
	/**
	 * checks if any of these locations appear in the room
	 * @param ids the ids that you want to find
	 * @return true if at least one exists inside the room
	 */
	public boolean containsOneOfLocations(int ... ids) {
		Iterator<SceneObject[]> it = getLocations().values().iterator();
		while (it.hasNext()) {
			for (SceneObject l : it.next()) {
				for (int i = 0; i < ids.length; i++) {
					if (l.getId() == ids[i])
						return true;
				}
			}
		}
		return false;
	}
	
	/**
	 * checks whether this room contains the given tile
	 * @param x x coord of the tile
	 * @param y y coord of the tile
	 * @return true if the tile is in the room
	 */
	public boolean containsTile(int x, int y) {
		return area.contains(x, y);
	}
	
	
	/**
	 * gets the monsters inside this room
	 * @return the monster list
	 */
	public ArrayList<NPC> getMonsters() {
		if (!monstersInitialised || (monstersInitialised && monsters.size() > 0 && !getArea().contains(monsters.get(0).getLocation())))
			monsters = getArea().getNpcs();
		return monsters;
	}
	
	/**
	 * Defines the Area that is linked to this Room
	 */
	private void defineArea() {
		int topLeftX = centerX - 8;
		int topLeftY = centerY - 8;
		int bottomRightX = centerX + 7;
		int bottomRightY = centerY + 7;
		Tile[] tiles = new Tile[256];
		int counter = 0;
		for (int i = topLeftX; i <= bottomRightX; i++) {
			for (int j = topLeftY; j <= bottomRightY; j++) {
				tiles[counter++] = new Tile(i, j, Game.getPlane());
			}
		}
		area = new TileArea(this, tiles);
		if (area.isCompletelyLoaded()) {
			monsters = area.getNpcs();
			monstersInitialised = true;
		} else {
			monstersInitialised = false;
		}
	}
	
	/**
	 * Gets the Area of the room
	 * @return the TileArea of this room
	 */
	private TileArea getArea() {
		return area;
	}
	
	/**
	 * finds out whether our room is correctly initialised or not
	 * @return true if initialised
	 */
	public boolean isInitialised() {
		return monstersInitialised && doorsInitialised;
	}
	
	/**
	 * finds the center tile of the map based on the coordinates of the door that was used to enter the room
	 * @param x the x coordinate of the door that led to this room
	 * @param y the y coordinate of the door that led to this room
	 */
	private void setTileCoordinates(int x, int y, Direction direction) {
		switch (direction) {
			case NORTH:
				centerX = x;
				centerY = y - 7;
				break;
			case EAST:
				centerX = x - 7;
				centerY = y;
				break;
			case SOUTH:
				centerX = x;
				centerY = y + 8;
				break;
			case WEST:
				centerX = x + 8;
				centerY = y;
				break;
			default:
				break;
		}
	}
	
	/**
	 * sets the coordinations of this room on a 8x8 mapgrid
	 * @param x the x coordinate
	 * @param y the y coordinate
	 */
	private void setMapCoordinates(int x, int y) {
		this.mapX = x;
		this.mapY = y;
	}
	
	/**
	 * gets the centertile of this room
	 * @return the tile that represents the center of this Room
	 */
	public Tile getCenterTile() {
		return new Tile(centerX, centerY, Game.getPlane());
	}
	
	/**
	 * gets us the door at the direction that is given. ALLWAYS CHECK IF THE ROOM IS INITIALISED BEFORE CALLING THIS!!!!
	 * @param dir the direction we're looking at
	 * @return the door or null if none is found (or not initialised)
	 */
	public Door getDoor(Direction dir) {
		return doors.get(dir);
	}
	
	public String toString() {
		return "center: (" + centerX + ", " + centerY + ")";
	}

}
